package old.galaxyGeneration;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Random;
import java.lang.Math;
import java.util.Arrays;


public class Universe {
	
	//Galactic limitations
	private int starLimit;// = 6000;	
	private int maxLightYears;// = 10000;  //light years
	private int gridMultiplier = 1;// = 1;  //number of grid lines per light year
	private double scaleFactor;// = 1.4142;  //related numStars to starGrid size
	
	private double minStarDistance;// = 1.0;  //light years
	private double avgStarDistance;// = 4.5;  //light years
	
	//For galaxy creation purposes, radius (in ly) around a star that is treated as exclusive to that star
	private int radiusThree;// = 1;
	private int radiusTwo;// = 2;
	private int radiusOne;// = 3;
	
	private int distThree = radiusThree*gridMultiplier;
	private int distTwo = radiusTwo*gridMultiplier;
	private int distOne = radiusOne*gridMultiplier;
	
	//dimensional constraints for galaxy types
	private double clusterStdDev;// = 1000.0; //light years
	private double multiClusterStdDevMax;// = 500.0;  //light years
	private double multiClusterStdDevMin;// = 100.0;  //light years
	private int minNumStars = 30;
	
	
	/*User input parameters*/
	private int numSpiralArms;
	private int numClusters = 10;  //for MultiCluster option
	private int numStars;  //30 to 3000
	private String age;  //young, average, old
	private double earthLike;  //0.0 to 0.1
	private int randomnessDial;  //0 to 6
	private boolean slowZone;  //1 = yes, 0 = no
	
		
	//Cluster, MultiCluster, Uniform, Spiral, BarSpiral, Random
	private String galaxyPattern;
	private enum galaxyPattern {Cluster, MultiCluster, Uniform, Spiral, BarSpiral, Random};
	
	
	//Single, Binary, Trinary, Quad, Neutron, Black holes, Antimatter
	private double[] systemTypeRatio;
	
//	private double[] starTypeRatioCum = new double[]{
//			0.5,
//			0.8,
//			0.98,
//			0.99,
//			0.995,
//			0.9998,
//			1.0};
	
	//Galaxy Age considerations
	//-------------------------------------------------------------------
	
	 /* Dwarfs, sub-dwarfs, Main sequence, Sub-Giants, Giants, Bright Giants, 
	 * Supergiants*/
	private double[] starSpectrumAvg;
	private double[] starSpectrumYoung; 
	private double[] starSpectrumOld;
			
	/*
	private double[] starSpectrumRatioOldCum = new double[]{
			0.7019999991,
			0.9019999991,
			0.9519999991,
			0.9919999991,
			0.9999999991,
			0.9999999999,
			1.0};
	//-------------------------------------------------------------------
	
	
	
	//Specials
	//-------------------------------------------------------------------
	//probability of various special objects being placed in-game
	private double[] specialObjectsPerGame = new double[]{
			0.2,  //Gods, 1 in 5 (per God)
			0.5,  //Outsiders, 1 in 2 (per ship)
			0.4,  //Dyson sphere, 2 in 5 (per sphere)
			0.4,  //Ringworld, 2 in 5 (per ring)
			0.05, //Melding plague residue, 1 in 20
			0.5  //Wormholes, 1 in 2 (per wormhole)
			
	};
	
	//probability of including various events in game
	//events go into an event pool where they are checked every turn
	private double[] specialEventsPerGame = new double[]{
			
			
	};
	*/
	public int[][] starGrid;  //used for placement of stars on map
	public int uniLength;  //universe Length, in ly
	public int workingLength;
	
	
	//Lists of universe objects
	private ArrayList<StarModel> stars = new ArrayList<StarModel>();
	private ArrayList<Habitables> habs = new ArrayList<Habitables>();
	
	
	
	public Universe(){};
	
	public Universe
			(int numStars,
			String age,
			double earthLike,
			int randomnessDial,
			boolean slowZone,
			String galaxyPattern)	
	{
		this.numStars = numStars;
		this.age = age;
		this.earthLike = earthLike;
		this.randomnessDial = randomnessDial;
		this.slowZone = slowZone;
		this.galaxyPattern = galaxyPattern;
		System.out.println("Constrctor");
	}
	
	//Initial creation of the universe
	public void createMap()
	{
		System.out.println("CreateMap");
		uniLength = (int)Math.round(2*numStars); //2*numStars/1.4142
		if (uniLength%2 > 0)
			{uniLength+=1;}
		
		//expand to appropriate number of grid points
		uniLength *= gridMultiplier;
		starGrid = new int[uniLength][uniLength];
		
		//actual working space where stars are to be placed.  Prevents cut-off
		workingLength = uniLength/2;
				
		this.galaxyGenerator();
		
		
		
		
		/*for(int i=0; i<numStars; i++)
		{			
			//make and place a star.  Discard if bad position.
			
		
		//create system for star.
		
		//create habitable objects for star.
		
		//add specials
		//end for loop
		
		//make event probabilities for entire universe.
		
		//assign starships, starting states, etc.
		}		*/
		
		
	}
	
	private void galaxyGenerator()
	{
		System.out.println("GalaxyGenerator");
		Random randG1 = new Random();
		Random randG2 = new Random();
		Random randUni = new Random();
		
		//stars put on map
		int starsPlaced = 0;
		//for multicluser, cluster centers put on map
		int centersPlaced = 0;
		int x = 0;
		int y = 0;
		
		//probabilities of star placement in the outer ring (1s) and inner ring (2s)
		double innerProb = 0.1;
		double outerProb = 0.2;
		
		System.out.println(uniLength);
		
		
		if (galaxyPattern.compareTo("Cluster") == 0)
		{
			System.out.println("Just inside Cluster");
			
			//Reset array (just in case)
			for (int i = 0; i < numStars; i++)
			{
				for (int j = 0; j < numStars; j++)
				{
					starGrid[i][j] = 0;
				}
			}
			
			
			for (starsPlaced = 0; starsPlaced < numStars; starsPlaced++)
			{
				double tempX = uniLength/4+randG1.nextGaussian()*workingLength/7+workingLength/2;
				double tempY = uniLength/4+randG2.nextGaussian()*workingLength/7+workingLength/2;
				
				x = (int)tempX;
				y = (int)tempY;
				
				//if within the grid and not near edges
				if ((x > 2) & (y > 2) & (x < (uniLength-3)) & (y < (uniLength-3)))
				{
					//check starGrid values at location
					if (starGrid[x][y] == 0)
					{
						stars.add(new StarModel(x, y));
						updateGrid(x,y);					
					}

					else if ((starGrid[x][y] == 1) & (randUni.nextDouble() < outerProb))
					{
						stars.add(new StarModel(x, y));
						updateGrid(x,y);						
					}

					else if ((starGrid[x][y] == 2) & (randUni.nextDouble() < innerProb))
					{
						stars.add(new StarModel(x, y));
						updateGrid(x,y);
					}
					else
					{starsPlaced -= 1;}
					//if outside the grid, subt 1 from i, loop again
				}
				else 
				{starsPlaced -= 1;}
				//System.out.println(stars.size());				
			}
			//System.out.println(starsPlaced);
			
			for (StarModel t : stars)
			{
				System.out.println(t.getX() + " " + t.getY());
			}
			
						
		}
		else if (galaxyPattern.compareTo("MultiCluster") == 0)
		{			
			System.out.println("Just inside MultiCluster");

			if (numStars < 100)
			{System.out.println("Need at least 100 stars!");
			return;}

			//Establish border region where no clusters may be placed.  10% of length
			double borderZone1 = uniLength*1/4;
			double borderZone2 = uniLength*3/4;

			//Minimum allowable distance between clusters.  Gets smaller with more clusters.
			double minClusterDist = (workingLength-minNumStars/1.25/2)/numClusters;
			double minClusterDev = workingLength/16;  //smallest std. deviation of stars in a cluster

			double[] starAlloc = new double[numClusters];
			double allocSum = 0;
			//Determine proportion of stars in each cluster
			for (int i = 0; i < numClusters; i++)
			{
				starAlloc[i] = randUni.nextDouble()*0.8+0.1;
				allocSum += starAlloc[i];				
			}

			Cluster[] clusters = new Cluster[numClusters];
			int tempSum = 0;
			//Normalize
			for (int i = 0; i < numClusters; i++)
			{
				clusters[i] = new Cluster((int)(numStars*starAlloc[i]/allocSum));
				tempSum += clusters[i].getNumStars();
			}
			
			if (tempSum > numStars)
			{clusters[0].setNumStars(clusters[0].getNumStars()-(tempSum - numStars));}
			else if (tempSum < numStars)
			{clusters[0].setNumStars(clusters[0].getNumStars()+(numStars - tempSum));}
			

			//Sort to put largest cluster first
			Arrays.sort(clusters, new ClusterComparator());			

			double tempX;
			double tempY;
			boolean distFlag;
			double clusterDeviation;

			for (centersPlaced = 0; centersPlaced < numClusters; centersPlaced++)
			{
				//System.out.println("Cluster Placement Loop");
				distFlag = true;

				//Determine standard deviation of cluster
				clusterDeviation = (randUni.nextDouble()*0.6+0.2)*clusters[centersPlaced].getNumStars()/8+clusters[centersPlaced].getNumStars()/5;

				//Place the largest cluster within uniLength/6 of center
				if (centersPlaced == 0)
				{
					//System.out.println(uniLength*(randUni.nextDouble()*0.333+0.333));
					tempX = uniLength/4+workingLength*(randUni.nextDouble()*0.333+0.333);//*uniLength/6+(uniLength/2-randUni.nextDouble()*uniLength/2);
					tempY = uniLength/4+workingLength*(randUni.nextDouble()*0.333+0.333);//*uniLength/6+(uniLength/2-randUni.nextDouble()*uniLength/2);					
				}
				//otherwise let them fall where they may
				else
				{
					
					tempX = uniLength/4+workingLength*(randUni.nextDouble()*0.8+0.1);//*uniLength/3+uniLength/2;
					tempY = uniLength/4+workingLength*(randUni.nextDouble()*0.8+0.1);//*uniLength/3+uniLength/2;					
				}
				
				//System.out.println("tempX = " +tempX + "tempY = " +tempY);

				//Ensure that the current center is far enough from the others
				for (int i = 0; i < centersPlaced; i++)
				{
					double distance = Math.sqrt((tempX-clusters[i].getX())*(tempX-clusters[i].getX()) + (tempY-clusters[i].getY())*(tempY-clusters[i].getY()));
					if (distance < minClusterDist)
					{distFlag = false;}
					System.out.println("Inside distance loop");
				}

				//If far enough away from other clusters
				if (distFlag == true)
				{	
					//if not in the border areas
					if ((tempX > borderZone1) & (tempY > borderZone1) & (tempX < borderZone2) & (tempY < borderZone2))// & (clusterDeviation > minClusterDev))
					{
						clusters[centersPlaced].setX((int)tempX);
						clusters[centersPlaced].setY((int)tempY);
						clusters[centersPlaced].setDev(clusterDeviation);						
					}				
					else
					{centersPlaced -= 1;}			
				}		
				else
				{centersPlaced -= 1;}

				System.out.println("clusterDev = " + clusterDeviation);
				System.out.println("x = " + tempX + " y = " + tempY);				

			}
			
			
				//System.out.println("centersPlaced = " +centersPlaced);
				/*try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();					
				}*/				
			
			System.out.println("Centers Placed Done");
			int devSum = 0;
			//int tempSum = 0;
			
			/*//Generate stars around each cluster
			for (int i = 0; i < numClusters; i++)
			{devSum += clusters[i].getDev();}
			
			//System.out.println("devSum = " + devSum);
			//Calculate number of stars per cluster
			int[] maxStarPerCluster = new int[numClusters];
			for (int i = 0; i < numClusters; i++)
			{
				maxStarPerCluster[i] = numStars * clusterCenters[i][2]/devSum;
				tempSum += maxStarPerCluster[i];
			}
			
			//Adjustment to keep total number of stars constant
			//(rounding errors above may have lost or gained a few stars)
			if (tempSum > numStars)
			{maxStarPerCluster[0] -= tempSum - numStars;}
			else if (tempSum < numStars)
			{maxStarPerCluster[0] += numStars - tempSum;}
			
			for (int i = 0; i < numClusters; i++)
			{
				System.out.println(maxStarPerCluster[i]);
			}*/
			
			
			//Place stars at each cluster (do so evenly)
			int[] placedPerCluster = new int[numClusters];
			starsPlaced = 0;
			System.out.println("Before While Loop");
			while (starsPlaced < numStars)
			{
				for (int i = 0; i < numClusters; i++)
				{
					if (placedPerCluster[i] < clusters[i].getNumStars())
					{
						tempX = randG1.nextGaussian()*clusters[i].getDev()+clusters[i].getX();
						tempY = randG2.nextGaussian()*clusters[i].getDev()+clusters[i].getY();

						x = (int)tempX;
						y = (int)tempY;

						if ((x > 2) & (y > 2) & (x < (uniLength-3)) & (y < (uniLength-3)))
						{
							placedPerCluster[i]++;
							starsPlaced++;
							//check starGrid values at location
							if (starGrid[x][y] == 0)
							{
								stars.add(new StarModel(x, y));
								updateGrid(x,y);					
							}

							else if ((starGrid[x][y] == 1) & (randUni.nextDouble() < outerProb))
							{
								stars.add(new StarModel(x, y));
								updateGrid(x,y);						
							}

							else if ((starGrid[x][y] == 2) & (randUni.nextDouble() < innerProb))
							{
								stars.add(new StarModel(x, y));
								updateGrid(x,y);
							}
							else
							{
								starsPlaced--;
								placedPerCluster[i]--;
							}						
						}						
					}
				}
				//System.out.println(starsPlaced);
			}
			for (StarModel t : stars)
			{
				System.out.println(t.getX() + " " + t.getY());
			}
			
			
			
			
			
		}
		else if (galaxyPattern.compareTo("Uniform") == 0)
		{
			for (starsPlaced = 0; starsPlaced < numStars; starsPlaced++)
			{

				double tempX = uniLength/4+randUni.nextDouble()*workingLength;
				double tempY = uniLength/4+randUni.nextDouble()*workingLength;

				x = (int)tempX;
				y = (int)tempY;

				if ((x > 2) & (y > 2) & (x < (uniLength-3)) & (y < (uniLength-3)))
				{
					//check starGrid values at location
					if (starGrid[x][y] == 0)
					{
						stars.add(new StarModel(x, y));
						updateGrid(x,y);					
					}

					else if ((starGrid[x][y] == 1) & (randUni.nextDouble() < outerProb))
					{
						stars.add(new StarModel(x, y));
						updateGrid(x,y);						
					}

					else if ((starGrid[x][y] == 2) & (randUni.nextDouble() < innerProb))
					{
						stars.add(new StarModel(x, y));
						updateGrid(x,y);
					}
					else
					{starsPlaced -= 1;}
					//if outside the grid, subt 1 from i, loop again
				}
				else if (randUni.nextDouble() < outerProb) 
				{starsPlaced -= 1;}
				//System.out.println(stars.size());				
			}
			for (StarModel t : stars)
			{
				System.out.println(t.getX() + " " + t.getY());
			}
			
			
		}
		
		
		else if (galaxyPattern.compareTo("Spiral") == 0)
		{
			
		}
		else if (galaxyPattern.compareTo("BarSpiral") == 0)
		{
			
		}
		else if (galaxyPattern.compareTo("Random") == 0)
		{
			
		}
		else
		{
			
		}
		
	}
	
	public void updateGrid(int x, int y)
	{
		//Center region
		starGrid[x][y] = 3;

		//fill in 3s
		starGrid[x+1][y] = (starGrid[x+1][y] < 3) ? (3):(starGrid[x+1][y]);
		starGrid[x+1][y+1] = (starGrid[x+1][y+1] < 3) ? (3):(starGrid[x+1][y+1]);
		starGrid[x+1][y-1] = (starGrid[x+1][y-1] < 3) ? (3):(starGrid[x+1][y-1]);
		starGrid[x][y+1] = (starGrid[x][y+1] < 3) ? (3):(starGrid[x][y+1]);
		starGrid[x][y-1] = (starGrid[x][y-1] < 3) ? (3):(starGrid[x][y-1]);
		starGrid[x-1][y] = (starGrid[x-1][y] < 3) ? (3):(starGrid[x-1][y]);
		starGrid[x-1][y+1] = (starGrid[x-1][y+1] < 3) ? (3):(starGrid[x-1][y+1]);
		starGrid[x-1][y-1] = (starGrid[x-1][y-1] < 3) ? (3):(starGrid[x-1][y-1]);

		//fill in 2s
		for (int i=x-2; i<x+3; i++)
		{
			starGrid[i][y+2] = (starGrid[i][y+2] < 2) ? (2):(starGrid[i][y+2]);
			starGrid[i][y-2] = (starGrid[i][y-2] < 2) ? (2):(starGrid[i][y-2]);
		}

		for (int i=y-1; i<y+2; i++)
		{
			starGrid[x-2][i] = (starGrid[x-2][i] < 2) ? (2):(starGrid[x-2][i]);
			starGrid[x+2][i] = (starGrid[x+2][i] < 2) ? (2):(starGrid[x+2][i]);
		}

		//fill in 1s
		for (int i=x-3; i<x+4; i++)
		{
			starGrid[i][y+3] = (starGrid[i][y+3] < 1) ? (1):(starGrid[i][y+3]);
			starGrid[i][y-3] = (starGrid[i][y-3] < 1) ? (1):(starGrid[i][y-3]);
		}

		for (int i=y-2; i<y+3; i++)
		{
			starGrid[x-3][i] = (starGrid[x-3][i] < 1) ? (1):(starGrid[x-3][i]);
			starGrid[x+3][i] = (starGrid[x+3][i] < 1) ? (1):(starGrid[x+3][i]);
		}
	}
	
	public void printStars()
	{
		for (StarModel t : stars)
		{
			//System.out.println(t.getY());
			System.out.print(stars.size());
			//System.out.println(t.getX());			
		}
	}
	
	//NOTE:  Shouldn't most of this be a function of the specific objects rather than
	//assigned via the universe object?
	
	//called from createMap.  Adds specials to planets, etc.
	private void makeSpecials(){}
	
	//called from createMap.  Holds list of specials with probabilities for reference throughout game.
	private void makeEventProbabilities(){}
	
	//called from createMap or updateMap.  Create a system.
	private void makeSystem(){}	
	
	//called from makeSystem
	private void makeHabitableObject(){}
	

	
	
	//Adds, removes, or modifies any object on map.
	//NOTE:  Shouldn't update capabilities be within each object rather than accessed
	//through the universe object?
	public void updateMap(){}
	
	private void updateSpecials(){}
	
	private void updateEventProbabilities(){}
	
	private void updateSystem(){}
	
	private void updateHabitableObject(){}
	
	
	public static void main(String[] args)
	{
		
	}
	
	class ClusterComparator implements Comparator<Object> {

		public int compare(Object clust1, Object clust2) {
			int clust1Stars = ((Cluster)clust1).numStars;
			int clust2Stars = ((Cluster)clust2).numStars;
			
			if (clust1Stars > clust2Stars)
				return -1;
			else if (clust1Stars < clust2Stars)
				return 1;
			else
				return 0;
			
		}
		
		
	}
	
	public void setStarLimit(int starLimit)
	{this.starLimit = starLimit;}
	
	public int getStarLimit()
	{return this.starLimit;}

	public double getAvgStarDistance() 
	{return avgStarDistance;}

	public void setAvgStarDistance(double avgStarDistance) 
	{this.avgStarDistance = avgStarDistance;}

	public double getClusterStdDev() 
	{return clusterStdDev;}

	public void setClusterStdDev(double clusterStandardDev) 
	{this.clusterStdDev = clusterStandardDev;}

	public int getGridMultiplier() 
	{return gridMultiplier;}

	public void setGridMultiplier(int gridMultiplier) 
	{this.gridMultiplier = gridMultiplier;}

	public int getMaxLightYears() 
	{return maxLightYears;}

	public void setMaxLightYears(int maxLightYears) 
	{this.maxLightYears = maxLightYears;}

	public double getMinStarDistance() 
	{return minStarDistance;}

	public void setMinStarDistance(double minStarDistance) 
	{this.minStarDistance = minStarDistance;}

	public double getMultiClusterStdDevMax() 
	{return multiClusterStdDevMax;}

	public void setMultiClusterStdDevMax(double multiClusterStandardDevMax) 
	{this.multiClusterStdDevMax = multiClusterStandardDevMax;}

	public double getMultiClusterStdDevMin() 
	{return multiClusterStdDevMin;}

	public void setMultiClusterStdDevMin(double multiClusterStandardDevMin) 
	{this.multiClusterStdDevMin = multiClusterStandardDevMin;}

	public int getRadiusOne() 
	{return radiusOne;}

	public void setRadiusOne(int radiusOne) 
	{this.radiusOne = radiusOne;}

	public int getRadiusThree() 
	{return radiusThree;}

	public void setRadiusThree(int radiusThree) 
	{this.radiusThree = radiusThree;}

	public int getRadiusTwo() 
	{return radiusTwo;}

	public void setRadiusTwo(int radiusTwo) 
	{this.radiusTwo = radiusTwo;}

	public double[] getSystemTypeRatio() 
	{return systemTypeRatio;}

	public void setSystemTypeRatio(double[] systemTypeRatio) 
	{this.systemTypeRatio = systemTypeRatio;}

	public double[] getStarSpectrumAvg() 
	{return starSpectrumAvg;}

	public void setStarSpectrumAvg(double[] starSpectrumRatioAvg) 
	{this.starSpectrumAvg = starSpectrumRatioAvg;}

	public double[] getStarSpectrumYoung() 
	{return starSpectrumYoung;}

	public void setStarSpectrumYoung(double[] starSpectrumRatioYoung) 
	{this.starSpectrumYoung = starSpectrumRatioYoung;}

	public double[] getStarSpectrumOld() 
	{return starSpectrumOld;}

	public void setStarSpectrumOld(double[] starSpectrumRatioOld) 
	{this.starSpectrumOld = starSpectrumRatioOld;}

	public double getScaleFactor() 
	{return scaleFactor;}

	public void setScaleFactor(double scaleFactor) 
	{this.scaleFactor = scaleFactor;}
	
	
	
	

}
